package com.loyal.camerax.utils

import android.content.Context
import android.graphics.ImageFormat
import android.graphics.Point
import android.net.Uri
import android.util.Log
import androidx.camera.core.AspectRatio
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraProvider
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraState
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.FocusMeteringResult
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.core.resolutionselector.AspectRatioStrategy
import androidx.camera.view.PreviewView
import androidx.lifecycle.LifecycleOwner
import com.loyal.camerax.config.PhotoConfig
import com.loyal.camerax.config.PhotoConfig.Companion.saveImageByteArray
import com.loyal.camerax.impl.FocusCallback
import com.loyal.camerax.impl.TouchEventListener
import com.loyal.camerax.view.FocusImageView
import java.io.File
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

const val TAG = "Camerax.kt"
const val RATIO_4_3_VALUE = 4.0 / 3.0
const val RATIO_16_9_VALUE = 16.0 / 9.0

fun aspectRatio(width: Int, height: Int): AspectRatioStrategy {
    val previewRatio = max(width, height).toDouble() / min(width, height)
    if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
        return AspectRatioStrategy.RATIO_4_3_FALLBACK_AUTO_STRATEGY
    }
    return AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY
}

fun Camera?.zoomAndFocus(
    previewView: PreviewView, focusView: FocusImageView, cameraExecutor: Executor
) {
    val cameraControl = this?.cameraControl
    val cameraInfo = this?.cameraInfo
    val zoomState = cameraInfo?.zoomState
    val focusCallback = TouchEventListener(focusView.context)
    focusCallback.setFocusCallback(object : FocusCallback {
        override fun zoom(delta: Float) {
            Log.e(TAG, "zoom: ->$delta")
            zoomState?.value?.let {
                val currentZoomRatio = it.zoomRatio
                cameraControl?.setZoomRatio(currentZoomRatio * delta)
            }
        }

        override fun click(x: Float, y: Float) {
            val factory = previewView.meteringPointFactory
            val point = factory.createPoint(x, y)
            val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                //.addPoint(point2, FocusMeteringAction.FLAG_AE) //可多个点
                .setAutoCancelDuration(3, TimeUnit.SECONDS)
                .build()
            focusView.startFocus(Point(x.toInt(), y.toInt()))

            val future = cameraControl?.startFocusAndMetering(action)
            future?.addListener({
                try {
                    val result = future.get() as FocusMeteringResult
                    focusView.post { focusView.focus(result.isFocusSuccessful) }
                } catch (e: Exception) {
                    focusView.post {
                        focusView.focusError(e)
                    }
                }
            }, cameraExecutor)
        }

        override fun doubleClick(x: Float, y: Float) {
            // 双击放大缩小
            zoomState?.value?.let {
                val currentZoomRatio = it.zoomRatio
                if (currentZoomRatio > it.minZoomRatio) {
                    cameraControl?.setLinearZoom(0f)
                } else {
                    cameraControl?.setLinearZoom(0.5f)
                }
            }
        }

        override fun longClick(x: Float, y: Float) {
            Log.e(TAG, "longClick: ")
        }
    })
    previewView.setOnTouchListener(focusCallback)
    previewView.setOnClickListener {
        Log.e(TAG, "zoomAndFocus: click")
    }
}

fun CameraProvider?.hasBackCamera(): Boolean {
    return this?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
}

fun CameraProvider?.hasFrontCamera(): Boolean {
    return this?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
}

fun CameraProvider?.canSwitchCamera(): Boolean {
    return hasBackCamera() && hasFrontCamera()
}

const val PHOTO_TYPE = "image/jpeg"
private const val FILENAME = "yyyyMMdd-HHmmss"

fun ImageCapture?.takePhoto(
    context: Context?, savePath: String?, saveName: String?,
    cameraExecutor: Executor, savedCallback: ((saveUri: Uri?) -> Unit)?,
) {
    if (null == context) return

    this?.let { imageCapture ->
        val newPath = if (savePath.isNullOrEmpty()) {
            context.filesDir.path
        } else savePath

        val fileName = if (saveName.isNullOrEmpty()) {
            SimpleDateFormat(FILENAME, Locale.US)
                .format(System.currentTimeMillis())
        } else saveName

        Log.e(TAG, "filePath: $newPath")
        Log.e(TAG, "fileName: $fileName")
        val outputOptions = ImageCapture.OutputFileOptions.Builder(
            File(newPath, "$fileName.jpg")
        ).build()

        imageCapture.takePicture(
            outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
                override fun onError(e: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${e.message}", e)
                    savedCallback?.invoke(null)
                }

                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val savedUri = output.savedUri
                    Log.d(TAG, "Photo capture succeeded: $savedUri")
                    savedCallback?.invoke(savedUri)
                }
            })
    }
}

fun ImageCapture?.takePhotoByByteArray(
    context: Context?, config: PhotoConfig,
    cameraExecutor: Executor, savedCallback: ((success: Boolean) -> Unit)?
) {
    if (null == context) return
    this?.takePicture(cameraExecutor, object : ImageCapture.OnImageCapturedCallback() {
        override fun onCaptureSuccess(image: ImageProxy) {
            super.onCaptureSuccess(image)
            savePicture(image, config, savedCallback)
        }

        override fun onError(e: ImageCaptureException) {
            super.onError(e)
            Log.e(TAG, "Photo capture failed: ${e.message}", e)
            savedCallback?.invoke(false)
        }
    })
}

fun savePicture(
    image: ImageProxy, config: PhotoConfig,
    savedCallback: ((success: Boolean) -> Unit)?
) {
    val imageInfo = image.imageInfo
    when (image.format) {
        ImageFormat.JPEG -> {
            val buffer = image.planes[0].buffer
            val data = buffer.toByteArray()
            val newConfig = config.copy(rotationAngle = imageInfo.rotationDegrees * 1f)
            val success = newConfig.saveImageByteArray(data)
            /*val success = ImageUtil.saveImageByteArray(
                data, imageFile.path, imageInfo.rotationDegrees * 1f
            )*/
            savedCallback?.invoke(success)
        }
    }
    image.close()
}

/**切换相机*/
fun switchCameraId(currentLensFacing: Int): Int {
    return if (CameraSelector.LENS_FACING_FRONT == currentLensFacing) {
        CameraSelector.LENS_FACING_BACK
    } else {
        CameraSelector.LENS_FACING_FRONT
    }
}

fun CameraInfo?.removeObservers(lifecycleOwner: LifecycleOwner) {
    this ?: return
    cameraState.removeObservers(lifecycleOwner)
    torchState.removeObservers(lifecycleOwner)
    zoomState.removeObservers(lifecycleOwner)
}

fun CameraInfo?.observeCameraState(lifecycleOwner: LifecycleOwner) {
    this ?: return
    cameraState.observe(lifecycleOwner) { cameraState ->
        when (cameraState.type) {
            CameraState.Type.PENDING_OPEN -> {
                // Ask the user to close other camera apps
                Log.i(TAG, "CameraState: Pending Open")
            }

            CameraState.Type.OPENING -> {
                // Show the Camera UI
                Log.i(TAG, "CameraState: Opening")
            }

            CameraState.Type.OPEN -> {
                // Setup Camera resources and begin processing
                Log.i(TAG, "CameraState: Open")
            }

            CameraState.Type.CLOSING -> {
                // Close camera UI
                Log.i(TAG, "CameraState: Closing")
            }

            CameraState.Type.CLOSED -> {
                // Free camera resources
                Log.i(TAG, "CameraState: Closed")
            }
        }

        cameraState.error?.let { error ->
            when (error.code) {
                // Open errors
                CameraState.ERROR_STREAM_CONFIG -> {
                    // Make sure to setup the use cases properly
                    Log.e(TAG, "Stream config error")
                }
                // Opening errors
                CameraState.ERROR_CAMERA_IN_USE -> {
                    // Close the camera or ask user to close another camera app that's using the
                    // camera
                    Log.e(TAG, "Camera in use")
                }

                CameraState.ERROR_MAX_CAMERAS_IN_USE -> {
                    // Close another open camera in the app, or ask the user to close another
                    // camera app that's using the camera
                    Log.e(TAG, "Max cameras in use")
                }

                CameraState.ERROR_OTHER_RECOVERABLE_ERROR -> {
                    Log.e(TAG, "Other recoverable error")
                }
                // Closing errors
                CameraState.ERROR_CAMERA_DISABLED -> {
                    // Ask the user to enable the device's cameras
                    Log.e(TAG, "Camera disabled")
                }

                CameraState.ERROR_CAMERA_FATAL_ERROR -> {
                    // Ask the user to reboot the device to restore camera function
                    Log.e(TAG, "Fatal error")
                }
                // Closed errors
                CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> {
                    // Ask the user to disable the "Do Not Disturb" mode, then reopen the camera
                    Log.e(TAG, "Do not disturb mode enabled")
                }
            }
        }
    }
}

fun ByteBuffer.toByteArray(): ByteArray {
    rewind()    // Rewind the buffer to zero
    val data = ByteArray(remaining())
    get(data)   // Copy the buffer into a byte array
    return data // Return the byte array
}
